package cn.xw.commons.core.utils.fileServerUtils;

import cn.xw.commons.core.utils.fileServerUtils.domain.entity.FileStream;
import lombok.RequiredArgsConstructor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 文件服务器的垃圾文件收集清理工具（防止上传文件后，都入文件服务器了，但未被业务使用上）
 *
 * @author Anhui AntLaddie <a href="https://www.cnblogs.com/antLaddie/">(博客园蚂蚁小哥)</a>
 * @version 1.0
 **/
@Component
@RequiredArgsConstructor
public class FastDFSBeanToolsProxy {

    // 属性注入
    private final RedisTemplate<String, String> redisTemplate;

    // FastDFS缓存Key
    private final static String FASTDFS_CACHE_KEY = "fastDFS_cache_key";
    // 定时清理FastDFS服务器的间隔时长；单位毫秒（此时设置1小时）
    private final static long TIMING_DURATION = 1000 * 60 * 60L;
    // 清理Redis缓存的文件最大存活时间（每次清理则清理之前30分钟的信息）
    private final static Integer CLEAN_TIME_RANGE = 2;

    /***
     * FastDFS文件上传（单个上传）（上传文件到服务器时结束后，紧接插入一条缓存到redis，防止业务图片未入数据库而变为垃圾文件，后期定时清理））
     * @param fileStream 文件信息
     * @return 上传成功的图片url信息
     */
    public String uploadFile(FileStream fileStream) {
        // 返回其代理对象并调用上传方法
        return enhancedMethodProxy().uploadFile(fileStream);
    }

    /***
     * FastDFS文件上传（多个上传）（上传文件到服务器时结束后，紧接插入一条缓存到redis，防止业务图片未入数据库而变为垃圾文件，后期定时清理））
     * @param fileStreamList 多个文件信息
     * @return 上传成功的图片url信息
     */
    public List<String> uploadFiles(List<FileStream> fileStreamList) {
        // 返回其代理对象并调用上传方法
        return enhancedMethodProxy().uploadFiles(fileStreamList);
    }

    /***
     * 根据URL信息删除具体资源信息（在文件具体被业务流程入库到数据库后，代表当前文件为非垃圾文件，则需要手动清理缓存）
     * @param url url资源（标识具体的文件信息）（方便通过key查找到缓存）
     */
    public Integer deleteUrl(String url) {
        // 返回其代理对象并调用删除方法
        return enhancedMethodProxy().deleteUrl(url);
    }

    /***
     * 根据URL信息删除缓存（因为业务图片一旦入业务数据库则后面无需删除文件服务器文件了）
     * <h3 style="color:#f00;">注意：文件一旦入业务数据库后需要手动调用此方法来告知缓存已经无需自动清理此文件了</h3>
     * @param url url资源
     */
    public void deleteFileCache(String url) {
        fileDelCleanCache(url);
    }

    /***
     * 增强方法代理（这里我就没再构建一个类去实现MethodInterceptor接口了，而是直接实现）
     * @return 代理成功的类，注：调用这个被返回的对象都是有方法前后增强的
     */
    public FastDFSBeanTools enhancedMethodProxy() {
        // 创建Cglib代理器并设置基本信息（MethodInterceptor则是对其具体增强）
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FastDFSBeanTools.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                // 方法前增强
                // ........
                // 执行uploadFile的单个文件上传方法
                if ("uploadFile".equals(method.getName())) {
                    String url = (String) methodProxy.invokeSuper(o, objects);
                    fileUploadCache(url);   // 资源缓存
                    return url;
                }
                // 执行uploadFiles的多个文件上传方法
                if ("uploadFiles".equals(method.getName())) {
                    List<String> urls = (List<String>) methodProxy.invokeSuper(o, objects);
                    urls.forEach(url -> fileUploadCache(url));  // 批量添加缓存
                    return urls;
                }
                // 执行deleteUrl的单个文件删除方法
                if ("deleteUrl".equals(method.getName())) {
                    Object errno = methodProxy.invokeSuper(o, objects);
                    fileDelCleanCache((String) objects[0]); // 删除redis缓存
                    return errno;
                }
                return null;
            }
        });
        // 创建其具体的被代理类返回，由具体业务调用
        return (FastDFSBeanTools) enhancer.create();
    }

    /***
     * 文件上传缓存
     * @param url 上传成功的url信息
     */
    public void fileUploadCache(String url) {
        // 不为空则缓存
        if (url != null && !"".equals(url.trim())) {
            // url处理成组和具体资源路径信息
            String[] urlArr = FastDFSTools.urlAnalysis(url);
            url = urlArr[0] + "/" + urlArr[1];
            // 构建Redis的Hash类型操作方法并插入
            HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
            // 注：插入的是时间戳（方便知道当前图片什么时候插入，好后期在某个时间段清理）
            opsForHash.put(FASTDFS_CACHE_KEY, url, String.valueOf(new Date().getTime()));
        }
    }

    /***
     * 文件删除清理缓存（若文件提前删除了则需要删除redis缓存）
     * @param url url资源定位信息
     */
    public void fileDelCleanCache(String url) {
        // 不为空则删除缓存
        if (url != null && !"".equals(url.trim())) {
            // url处理成组和具体资源路径信息
            String[] urlArr = FastDFSTools.urlAnalysis(url);
            url = urlArr[0] + "/" + urlArr[1];
            // 构建Redis的Hash类型操作方法并删除缓存
            HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
            opsForHash.delete(FASTDFS_CACHE_KEY, url);
        }
    }

    /***
     * Scheduled注解说明：
     *      fixedDelay：固定延迟。这是一个以毫秒为单位的整数，表示任务执行之间的固定延迟。
     *          当上一个任务执行完毕后，才会执行下一个任务，直到达到指定的延迟。
     *      initialDelay：初始延迟。这是一个以毫秒为单位的整数，表示任务在开始执行前的延迟。
     *          在第一个任务执行之前，会等待指定的延迟时间。
     *      fixedRate：固定间隔。这是一个以毫秒为单位的整数，表示任务执行之间的固定间隔。
     *          无论上一个任务是否执行完毕，都会按照指定的间隔执行任务。
     * 定时清理用户上传的垃圾文件资源（防止无关联的垃圾资源占用空间）
     */
    @Scheduled(fixedRate = TIMING_DURATION , initialDelay = TIMING_DURATION )
    public void RegularlyCleanFileServerJunkFiles() {
        // 获取一个基本的日历类
        Calendar calendar = Calendar.getInstance();
        // 获取前30分钟的时间
        Calendar currentDate = Calendar.getInstance();
        currentDate.add(Calendar.MINUTE, -CLEAN_TIME_RANGE);

        // 构建Redis的Hash类型操作方法并查询全部FASTDFS_CACHE_KEY里的数据key
        HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
        List<Object> keys = new ArrayList<>(opsForHash.keys(FASTDFS_CACHE_KEY));
        // 查询所有value信息，并循环处理（查询的values信息和keys信息也是依次对应）
        List<Object> values = opsForHash.multiGet(FASTDFS_CACHE_KEY, keys);
        for (int i = 0; i < values.size(); i++) {
            // 设置时间
            calendar.setTimeInMillis(Long.parseLong(String.valueOf(values.get(i))));
            // 清理超过currentDate时间还未入业务数据的文件缓存
            if (calendar.compareTo(currentDate) < 0) {
                // 删除垃圾文件(缓存+实际垃圾文件)
                deleteUrl(String.valueOf(keys.get(i)));
            }
        }
    }
}
